package com.hsgene.gateway.filter;

import com.hsgene.common.util.basic.JsonUtils;
import com.hsgene.common.util.exception.CommonErrorCode;
import com.hsgene.common.util.result.ApiResult;
import com.netflix.client.ClientException;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ConnectException;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ERROR_TYPE;

/**
 * 自定义异常处理过滤器
 *
 * @author wangbing
 * @version 1.0, 2018/6/20
 */
@Component
public class MsErrorFilter extends ZuulFilter {

    private final static Logger LOGGER = LoggerFactory.getLogger(MsErrorFilter.class);

    protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";

    @Autowired
    private Environment environment;

    @Value("${global.exception.enable:false}")
    private String globalExceptionEnable;

    @Override
    public String filterType() {
        return ERROR_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        // only forward to errorPath if it hasn't been forwarded to already
        return ctx.getThrowable() != null
                && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        try {
            LOGGER.debug("网关发生异常：", ctx.getThrowable());
            Throwable throwable = getOriginException(ctx.getThrowable());
            LOGGER.error("网关发生异常：", throwable);
            if (throwable instanceof ClientException) {
                LOGGER.error("无可用的服务");
                responseFail(ctx, buildUnavailableResult(ctx, throwable));
            } else if (throwable instanceof ConnectException) {
                LOGGER.error("服务异常掉线导致服务不可用");
                responseFail(ctx, buildUnavailableResult(ctx, throwable));
            } else {
                if (isGlobalExceptionEnable()) {
                    responseFail(ctx, ApiResult.fail(CommonErrorCode.GATEWAY_SERVICE_EXCEPTION, throwable.getMessage()));
                } else {
                    responseFail(ctx, ApiResult.fail(CommonErrorCode.GATEWAY_SERVICE_EXCEPTION));
                }
            }
        } catch (Exception e) {
            LOGGER.error("ErrorFilter Exception:", e);
            ReflectionUtils.rethrowRuntimeException(e);
        }
        return null;
    }

    private ApiResult buildUnavailableResult(RequestContext ctx, Throwable throwable) {
        String proxy = ctx.get("proxy").toString();
        String serviceInfo = environment.getProperty("zuul.routes." + proxy + ".service_info", String.class);
        String description = serviceInfo + "不可用";
        if (isGlobalExceptionEnable()) {
            description += ":" + throwable.getMessage();
        }
        return ApiResult.fail(CommonErrorCode.SERVICE_NOT_AVAILABLE, description);
    }

    /**
     * 获取导致异常的直接原因
     *
     * @param throwable 异常信息
     * @return 直接异常
     */
    private Throwable getOriginException(Throwable throwable) {
        throwable = throwable.getCause();
        while (throwable.getCause() != null) {
            throwable = throwable.getCause();
        }
        return throwable;
    }

    /**
     * 失败返回
     *
     * @param ctx        请求上下文
     * @param failResult 失败信息
     */
    private void responseFail(RequestContext ctx, ApiResult failResult) {
        HttpServletResponse response = ctx.getResponse();
        response.setContentType("application/json; charset=utf8");
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            writer.print(JsonUtils.objectToJsonIgnoreNull(failResult));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

    /**
     * 是否返回异常信息
     *
     * @return 是否返回异常信息
     */
    private boolean isGlobalExceptionEnable() {
        return Boolean.TRUE.toString().equalsIgnoreCase(globalExceptionEnable);
    }
}
